From: Igor Opaniuk Date: Wed, 11 Sep 2024 16:03:10 +0000 (+0200) Subject: sysroot: Support boot counting for boot entries X-Git-Tag: archive/raspbian/2025.7-2+rpi1^2^2~6^2~4^2~1^2 X-Git-Url: https://dgit.raspbian.org/%22http:/www.example.com/cgi/%22https://%22%22/%22http:/www.example.com/cgi/%22https:/%22%22?a=commitdiff_plain;h=08487091256b93493f8d692e37ab3d892c758da1;p=ostree.git sysroot: Support boot counting for boot entries Add support for boot counting for bootloader entries [1]. The boot counting data is stored in the name of the boot loader entry. A boot loader entry file name may contain a plus (+) followed by a number. This may optionally be followed by a minus (-) followed by a second number. The dot (.) and file name suffix (conf or efi) must immediately follow. The feature is enabled via sysroot configuration: [sysroot] boot-counting-tries=3 Testing: $ ostree admin deploy 91fc19319be9e79d07159303dff125f40f10e5c25614630dcbed23d95e36f907 Copying /etc changes: 2 modified, 3 removed, 4 added bootfs is sufficient for calculated new size: 0 bytes Transaction complete; bootconfig swap: yes; bootversion: boot.0.1, deployment count change: 1 $ ls /boot/loader/entries ostree-1.conf ostree-2+3.conf [1] https://uapi-group.org/specifications/specs/boot_loader_specification/#boot-counting Signed-off-by: Igor Opaniuk Signed-off-by: Colin Walters --- diff --git a/Makefile-libostree.am b/Makefile-libostree.am index 463b809a..5a17e446 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -110,6 +110,7 @@ libostree_1_la_SOURCES = \ src/libostree/ostree-soft-reboot.c \ src/libostree/ostree-impl-system-generator.c \ src/libostree/ostree-bootconfig-parser.c \ + src/libostree/ostree-bootconfig-parser-private.h \ src/libostree/ostree-deployment.c \ src/libostree/ostree-bootloader.h \ src/libostree/ostree-bootloader.c \ diff --git a/Makefile-tests.am b/Makefile-tests.am index 57695e18..f5e15c23 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -122,6 +122,7 @@ _installed_or_uninstalled_test_scripts = \ tests/test-osupdate-dtb.sh \ tests/test-admin-instutil-set-kargs.sh \ tests/test-admin-upgrade-not-backwards.sh \ + tests/test-admin-boot-counting-tries.sh \ tests/test-admin-pull-deploy-commit.sh \ tests/test-admin-pull-deploy-split.sh \ tests/test-admin-locking.sh \ @@ -280,7 +281,7 @@ endif _installed_or_uninstalled_test_programs = tests/test-varint tests/test-ot-unix-utils tests/test-bsdiff tests/test-otcore tests/test-mutable-tree \ tests/test-keyfile-utils tests/test-ot-opt-utils tests/test-ot-tool-util \ - tests/test-checksum tests/test-lzma tests/test-rollsum \ + tests/test-checksum tests/test-lzma tests/test-rollsum tests/test-bootconfig-parser-internals \ tests/test-basic-c tests/test-sysroot-c tests/test-pull-c tests/test-repo tests/test-include-ostree-h tests/test-kargs \ tests/test-rfc2616-dates tests/test-pem @@ -348,6 +349,10 @@ tests_test_kargs_SOURCES = src/libostree/ostree-kernel-args.c tests/test-kargs.c tests_test_kargs_CFLAGS = $(TESTS_CFLAGS) tests_test_kargs_LDADD = $(TESTS_LDADD) +tests_test_bootconfig_parser_internals_SOURCES = tests/test-bootconfig-parser-internals.c +tests_test_bootconfig_parser_internals_CFLAGS = $(TESTS_CFLAGS) +tests_test_bootconfig_parser_internals_LDADD = $(TESTS_LDADD) + tests_test_repo_finder_config_SOURCES = tests/test-repo-finder-config.c tests_test_repo_finder_config_CFLAGS = $(TESTS_CFLAGS) tests_test_repo_finder_config_LDADD = $(TESTS_LDADD) diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index a43d1079..7e02d2f6 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -39,6 +39,8 @@ ostree_bootconfig_parser_set ostree_bootconfig_parser_get ostree_bootconfig_parser_set_overlay_initrds ostree_bootconfig_parser_get_overlay_initrds +ostree_bootconfig_parser_get_tries_left +ostree_bootconfig_parser_get_tries_done OSTREE_BOOTCONFIG_PARSER OSTREE_IS_BOOTCONFIG_PARSER diff --git a/man/ostree.repo-config.xml b/man/ostree.repo-config.xml index d1c2e34e..a9cc75bd 100644 --- a/man/ostree.repo-config.xml +++ b/man/ostree.repo-config.xml @@ -408,6 +408,18 @@ License along with this library. If not, see . + + boot-counting-tries + Integer value controlling the number of maximum boot attempts. The boot + counting data is stored in the name of the boot loader entry. A boot loader entry file name + may contain a plus (+) followed by a number. This may optionally be followed by + a minus (-) followed by a second number. The dot (.) and file name suffix (conf or efi) must + immediately follow. More details in the + + The Boot Loader Specification + + + bls-append-except-default A semicolon separated string list of key-value pairs. For example: diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 610a36b7..ce58121d 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -36,4 +36,6 @@ global: ostree_sysroot_deployment_can_soft_reboot; ostree_sysroot_deployment_set_soft_reboot; ostree_sysroot_clear_soft_reboot; + ostree_bootconfig_parser_get_tries_left; + ostree_bootconfig_parser_get_tries_done; } LIBOSTREE_2025.2; diff --git a/src/libostree/ostree-bootconfig-parser-private.h b/src/libostree/ostree-bootconfig-parser-private.h new file mode 100644 index 00000000..16ccb0fc --- /dev/null +++ b/src/libostree/ostree-bootconfig-parser-private.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.0+ */ + +#pragma once + +#include "ostree-bootconfig-parser.h" + +G_BEGIN_DECLS + +const char *_ostree_bootconfig_parser_filename (OstreeBootconfigParser *self); + +G_END_DECLS diff --git a/src/libostree/ostree-bootconfig-parser.c b/src/libostree/ostree-bootconfig-parser.c index 4c3e80d0..2e08256d 100644 --- a/src/libostree/ostree-bootconfig-parser.c +++ b/src/libostree/ostree-bootconfig-parser.c @@ -17,16 +17,19 @@ #include "config.h" -#include "ostree-bootconfig-parser.h" +#include "ostree-bootconfig-parser-private.h" #include "otutil.h" struct _OstreeBootconfigParser { GObject parent_instance; - gboolean parsed; + char *filename; const char *separators; + guint64 tries_left; + guint64 tries_done; + GHashTable *options; /* Additional initrds; the primary initrd is in options. */ @@ -51,11 +54,88 @@ ostree_bootconfig_parser_clone (OstreeBootconfigParser *self) GLNX_HASH_TABLE_FOREACH_KV (self->options, const char *, k, const char *, v) g_hash_table_replace (parser->options, g_strdup (k), g_strdup (v)); + parser->filename = g_strdup (self->filename); parser->overlay_initrds = g_strdupv (self->overlay_initrds); return parser; } +/* + * Parses a suffix of two counters in the form "+LEFT-DONE" from the end of the + * filename (excluding file extension). + */ +static void +parse_bootloader_tries (const char *filename, guint64 *out_left, guint64 *out_done) +{ + *out_left = 0; + *out_done = 0; + + const char *counter = strrchr (filename, '+'); + if (!counter) + return; + counter += 1; + + guint64 tries_left = 0; + guint64 tries_done = 0; + + // Negative numbers are invalid + if (*counter == '-') + return; + + { + char *endp = NULL; + tries_left = g_ascii_strtoull (counter, &endp, 10); + if (endp == counter || (tries_left == G_MAXUINT64 && errno == ERANGE)) + return; + counter = endp; + } + + /* Parse done counter only if present */ + if (*counter == '-') + { + counter += 1; + char *endp = NULL; + tries_done = g_ascii_strtoull (counter, &endp, 10); + if (endp == counter || (tries_done == G_MAXUINT64 && errno == ERANGE)) + return; + } + + *out_left = tries_left; + *out_done = tries_done; +} + +/** + * ostree_bootconfig_parser_get_tries_left: + * @self: Parser + * + * Returns: Amount of boot tries left + * + * Since: 2025.2 + */ +guint64 +ostree_bootconfig_parser_get_tries_left (OstreeBootconfigParser *self) +{ + return self->tries_left; +} + +/** + * ostree_bootconfig_parser_get_tries_done: + * @self: Parser + * + * Returns: Amount of boot tries + */ +guint64 +ostree_bootconfig_parser_get_tries_done (OstreeBootconfigParser *self) +{ + return self->tries_done; +} + +const char * +_ostree_bootconfig_parser_filename (OstreeBootconfigParser *self) +{ + return self->filename; +} + /** * ostree_bootconfig_parser_parse_at: * @self: Parser @@ -70,7 +150,7 @@ gboolean ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, int dfd, const char *path, GCancellable *cancellable, GError **error) { - g_assert (!self->parsed); + g_assert (!self->filename); g_autofree char *contents = glnx_file_get_contents_utf8_at (dfd, path, NULL, cancellable, error); if (!contents) @@ -116,8 +196,10 @@ ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, int dfd, const self->overlay_initrds = (char **)g_ptr_array_free (g_steal_pointer (&overlay_initrds), FALSE); } - self->parsed = TRUE; + const char *basename = glnx_basename (path); + parse_bootloader_tries (basename, &self->tries_left, &self->tries_done); + self->filename = g_strdup (basename); return TRUE; } @@ -262,6 +344,7 @@ ostree_bootconfig_parser_finalize (GObject *object) { OstreeBootconfigParser *self = OSTREE_BOOTCONFIG_PARSER (object); + g_free (self->filename); g_strfreev (self->overlay_initrds); g_hash_table_unref (self->options); diff --git a/src/libostree/ostree-bootconfig-parser.h b/src/libostree/ostree-bootconfig-parser.h index 5fdad72e..1ef7b7cb 100644 --- a/src/libostree/ostree-bootconfig-parser.h +++ b/src/libostree/ostree-bootconfig-parser.h @@ -67,4 +67,10 @@ void ostree_bootconfig_parser_set_overlay_initrds (OstreeBootconfigParser *self, _OSTREE_PUBLIC char **ostree_bootconfig_parser_get_overlay_initrds (OstreeBootconfigParser *self); +_OSTREE_PUBLIC +guint64 ostree_bootconfig_parser_get_tries_left (OstreeBootconfigParser *self); + +_OSTREE_PUBLIC +guint64 ostree_bootconfig_parser_get_tries_done (OstreeBootconfigParser *self); + G_END_DECLS diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 1e27fc77..3ea1ff65 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -247,6 +247,7 @@ struct OstreeRepo GHashTable *bls_append_values; /* Parsed key-values from bls-append-except-default key in config. */ gboolean enable_bootprefix; /* If true, prepend bootloader entries with /boot */ + guint boot_counting; OstreeRepo *parent_repo; }; diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 4e2f47d0..ec13a218 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -3297,6 +3297,16 @@ reload_remote_config (OstreeRepo *self, GCancellable *cancellable, GError **erro static gboolean reload_sysroot_config (OstreeRepo *self, GCancellable *cancellable, GError **error) { + g_autofree char *boot_counting_str = NULL; + + if (!ot_keyfile_get_value_with_default_group_optional ( + self->config, "sysroot", "boot-counting-tries", "0", &boot_counting_str, error)) + return FALSE; + guint64 v; + if (!g_ascii_string_to_unsigned (boot_counting_str, 10, 0, 5, &v, error)) + return glnx_prefix_error (error, "Parsing sysroot.boot-counting-tries"); + self->boot_counting = (guint)v; + g_autofree char *bootloader = NULL; if (!ot_keyfile_get_value_with_default_group_optional (self->config, "sysroot", "bootloader", diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 98d296a2..740bc69f 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #endif #include "libglnx.h" +#include "ostree-bootconfig-parser-private.h" #include "ostree-core-private.h" #include "ostree-deployment-private.h" #include "ostree-linuxfsutil.h" @@ -1805,6 +1807,7 @@ static char * bootloader_entry_filename (OstreeSysroot *sysroot, guint n_deployments, OstreeDeployment *deployment) { + g_autofree char *bootconf_name = NULL; guint index = n_deployments - ostree_deployment_get_index (deployment); // Allow opt-out to dropping the stateroot in case of compatibility issues. // As of 2024.5, we have a new naming scheme because grub2 parses the *filename* and ignores @@ -1813,12 +1816,28 @@ bootloader_entry_filename (OstreeSysroot *sysroot, guint n_deployments, if (use_old_naming) { const char *stateroot = ostree_deployment_get_osname (deployment); - return g_strdup_printf ("ostree-%d-%s.conf", index, stateroot); + bootconf_name = g_strdup_printf ("ostree-%d-%s", index, stateroot); } else { - return g_strdup_printf ("ostree-%d.conf", index); + bootconf_name = g_strdup_printf ("ostree-%d", index); } + + if (!sysroot->repo->boot_counting) + return g_strdup_printf ("%s.conf", bootconf_name); + + guint max_tries = sysroot->repo->boot_counting; + OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment); + + if (!_ostree_bootconfig_parser_filename (bootconfig)) + return g_strdup_printf ("%s+%u.conf", bootconf_name, max_tries); + else if (!ostree_bootconfig_parser_get_tries_left (bootconfig) + && !ostree_bootconfig_parser_get_tries_done (bootconfig)) + return g_strdup_printf ("%s.conf", bootconf_name); + else + return g_strdup_printf ("%s+%" PRIu64 "-%" PRIu64 ".conf", bootconf_name, + ostree_bootconfig_parser_get_tries_left (bootconfig), + ostree_bootconfig_parser_get_tries_done (bootconfig)); } /* Given @deployment, prepare it to be booted; basically copying its @@ -1855,7 +1874,6 @@ install_deployment_kernel (OstreeSysroot *sysroot, int new_bootversion, const char *bootcsum = ostree_deployment_get_bootcsum (deployment); g_autofree char *bootcsumdir = g_strdup_printf ("ostree/%s-%s", osname, bootcsum); g_autofree char *bootconfdir = g_strdup_printf ("loader.%d/entries", new_bootversion); - g_autofree char *bootconf_name = bootloader_entry_filename (sysroot, n_deployments, deployment); if (!glnx_shutil_mkdir_p_at (sysroot->boot_fd, bootcsumdir, 0775, cancellable, error)) return FALSE; @@ -2162,8 +2180,11 @@ install_deployment_kernel (OstreeSysroot *sysroot, int new_bootversion, if (!glnx_opendirat (sysroot->boot_fd, bootconfdir, TRUE, &bootconf_dfd, error)) return FALSE; + g_autofree char *bootconf_filename + = bootloader_entry_filename (sysroot, n_deployments, deployment); + if (!ostree_bootconfig_parser_write_at (ostree_deployment_get_bootconfig (deployment), - bootconf_dfd, bootconf_name, cancellable, error)) + bootconf_dfd, bootconf_filename, cancellable, error)) return FALSE; return TRUE; @@ -4302,7 +4323,7 @@ ostree_sysroot_deployment_set_kargs_in_place (OstreeSysroot *self, OstreeDeploym OstreeBootconfigParser *new_bootconfig = ostree_deployment_get_bootconfig (deployment); ostree_bootconfig_parser_set (new_bootconfig, "options", kargs_str); - g_autofree char *bootconf_name + g_autofree char *bootconf_filename = bootloader_entry_filename (self, self->deployments->len, deployment); g_autofree char *bootconfdir = g_strdup_printf ("loader.%d/entries", self->bootversion); @@ -4310,7 +4331,7 @@ ostree_sysroot_deployment_set_kargs_in_place (OstreeSysroot *self, OstreeDeploym if (!glnx_opendirat (self->boot_fd, bootconfdir, TRUE, &bootconf_dfd, error)) return FALSE; - if (!ostree_bootconfig_parser_write_at (new_bootconfig, bootconf_dfd, bootconf_name, + if (!ostree_bootconfig_parser_write_at (new_bootconfig, bootconf_dfd, bootconf_filename, cancellable, error)) return FALSE; } diff --git a/tests/test-admin-boot-counting-tries.sh b/tests/test-admin-boot-counting-tries.sh new file mode 100755 index 00000000..b5e5c1a7 --- /dev/null +++ b/tests/test-admin-boot-counting-tries.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# +# SPDX-License-Identifier: LGPL-2.0+ + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +setup_os_repository "archive" "syslinux" + +${CMD_PREFIX} ostree --repo=sysroot/ostree/repo config set sysroot.boot-counting-tries 3 +v=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo config get sysroot.boot-counting-tries) +assert_streq "$v" 3 + +tap_ok "init boot counting tries" + +${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmain/x86_64-runtime +rev=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo rev-parse testos/buildmain/x86_64-runtime) +export rev + +${CMD_PREFIX} ostree admin deploy --karg=quiet --stateroot=testos testos:testos/buildmain/x86_64-runtime +entry=$(ls sysroot/boot/loader/entries/) +assert_streq "${entry}" ostree-1+3.conf + +tap_ok "deploy with boot counting" + +tap_end diff --git a/tests/test-bootconfig-parser-internals.c b/tests/test-bootconfig-parser-internals.c new file mode 100644 index 00000000..00b18d4a --- /dev/null +++ b/tests/test-bootconfig-parser-internals.c @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: LGPL-2.0+ + */ + +#include "config.h" +#define _OSTREE_PUBLIC +#include "../src/libostree/ostree-bootconfig-parser.c" + +static void +test_parse_tries_valid (void) +{ + guint64 left, done; + parse_bootloader_tries ("foo", &left, &done); + g_assert_cmpuint (left, ==, 0); + g_assert_cmpuint (done, ==, 0); + + parse_bootloader_tries ("foo+1", &left, &done); + g_assert_cmpuint (left, ==, 1); + g_assert_cmpuint (done, ==, 0); + + parse_bootloader_tries ("foo+1-2", &left, &done); + g_assert_cmpuint (left, ==, 1); + g_assert_cmpuint (done, ==, 2); + + parse_bootloader_tries ("foo+1-2.conf", &left, &done); + g_assert_cmpuint (left, ==, 1); + g_assert_cmpuint (done, ==, 2); +} + +static void +test_parse_tries_invalid (void) +{ + guint64 left, done; + + parse_bootloader_tries ("foo+1-", &left, &done); + g_assert_cmpuint (left, ==, 0); + g_assert_cmpuint (done, ==, 0); + + parse_bootloader_tries ("foo+-1", &left, &done); + g_assert_cmpuint (left, ==, 0); + g_assert_cmpuint (done, ==, 0); + + parse_bootloader_tries ("foo+1-a", &left, &done); + g_assert_cmpuint (left, ==, 0); + g_assert_cmpuint (done, ==, 0); + + parse_bootloader_tries ("foo+a-1", &left, &done); + g_assert_cmpuint (left, ==, 0); + g_assert_cmpuint (done, ==, 0); +} + +int +main (int argc, char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/bootconfig-parser/tries/valid", test_parse_tries_valid); + g_test_add_func ("/bootconfig-parser/tries/invalid", test_parse_tries_invalid); + return g_test_run (); +}